/*
 * File: stm32_usbh.c
 *
 * Change Logs:
 * Date           Author            Notes
 * 2017-10-30     ZYH            the first version
 */

#include <rtthread.h>
#include <rtdevice.h>
#include "board.h"
#include <stm32f4xx_hal_hcd.h>

#ifdef BSP_USBH_ENABLE_DBG
#define DBG_ENABLE
#endif
#define DBG_LVL DBG_LOG
#define DBG_TAG "hcd-l"
#include <rtdbg.h>

struct stm_hc
{
    struct uhcd hcd;
    HCD_HandleTypeDef host;
    struct upipe *pipe[15];
    unsigned short index;
};

static struct stm_hc _stm_hc;

#define OTG_FS_PORT 1

void OTG_FS_IRQHandler(void)
{
    rt_interrupt_enter();
    HAL_HCD_IRQHandler(&_stm_hc.host);
    rt_interrupt_leave();
}

void OTG_HS_IRQHandler(void)
{
    rt_interrupt_enter();
    HAL_HCD_IRQHandler(&_stm_hc.host);
    rt_interrupt_leave();
}

void HAL_HCD_SOF_Callback(HCD_HandleTypeDef *hhcd)//提供定时器
{
    //LOG_D("sof callback\n");
}

static __IO rt_bool_t connect_status = RT_FALSE;
void HAL_HCD_Connect_Callback(HCD_HandleTypeDef *hhcd)
{
    struct stm_hc *hc;

    hc = (struct stm_hc *)hhcd->pData;
    if (!connect_status)
    {
        LOG_D("connected\n");

        connect_status = RT_TRUE;
        rt_usbh_root_hub_connect_handler(&hc->hcd, OTG_FS_PORT, RT_FALSE);
    }
}

void HAL_HCD_Disconnect_Callback(HCD_HandleTypeDef *hhcd)
{
    struct stm_hc *hc;

    hc = (struct stm_hc *)hhcd->pData;
    if (connect_status)
    {
        LOG_D("disconnnect\n");

        connect_status = RT_FALSE;
        rt_usbh_root_hub_disconnect_handler(&hc->hcd, OTG_FS_PORT);
    }
}

void HAL_HCD_HC_NotifyURBChange_Callback(HCD_HandleTypeDef *hhcd, uint8_t chnum, HCD_URBStateTypeDef urb_state)
{
    struct urb *r;
    struct stm_hc *hc;
    upipe_t pipe;

    hc = (struct stm_hc *)hhcd->pData;
    pipe = hc->pipe[chnum];

    r = rt_usb_hcd_urb_first(pipe);
    if (!r)
    {
        LOG_W("urb null");
        return;
    }

    if (urb_state == URB_DONE)
    {
        r->actual_size = HAL_HCD_HC_GetXferCount(&hc->host, chnum);
        if (r->actual_size == 0)
            r->actual_size = r->size;

        LOG_D(" size %d\n", r->actual_size);

        rt_usb_hcd_event_urb_complete(r, UPIPE_STATUS_OK);        
    }
    else if (urb_state == URB_NOTREADY)
    {
        HAL_HCD_HC_Halt(&hc->host, pipe->pipe_index);
        HAL_HCD_HC_SubmitRequest(&hc->host,
                             pipe->pipe_index,
                             (pipe->ep.bEndpointAddress & 0x80) >> 7,
                             pipe->ep.bmAttributes,
                             !r->is_setup,
                             r->buf,
                             r->size,
                             0);
    }
    else
    {
        LOG_D("urbcb %d,%d", chnum, urb_state);
    }
}

static int drv_reset_port(struct uhcd *hcd, rt_uint8_t port)
{
    struct stm_hc *hc;

    hc = (struct stm_hc *)hcd->parent.user_data;

    HAL_HCD_ResetPort(&hc->host);

    return 0;
}

static int drv_pipe_xfer(struct uhcd *hcd, upipe_t pipe, rt_uint8_t token, void *buffer, int nbytes, int timeout)
{
    return -1;
}

static rt_uint8_t  drv_get_free_pipe_index(struct stm_hc *hc)
{
    rt_uint8_t idx;

    for (idx = 1; idx < 16; idx++)
    {
        if ((hc->index & (0x01 << idx)))
            continue;

        hc->index |= (0x01 << idx);
        return idx;
    }

    return 0xff;
}

static void drv_free_pipe_index(struct stm_hc *hc, rt_uint8_t index)
{
    hc->index &= ~(0x01 << index);
}

static int drv_open_pipe(struct uhcd *hcd, upipe_t pipe)
{
    struct stm_hc *hc;

    hc = (struct stm_hc *)hcd->parent.user_data;

    pipe->pipe_index = drv_get_free_pipe_index(hc);

    LOG_D("open pipe(%d) mps(%d)", pipe->pipe_index, pipe->ep.wMaxPacketSize);

    if (pipe->pipe_index == 0xff)
        return -1;

    HAL_HCD_HC_Init(&hc->host,
                    pipe->pipe_index,
                    pipe->ep.bEndpointAddress,
                    pipe->inst->address,
                    USB_OTG_SPEED_FULL,
                    pipe->ep.bmAttributes,
                    pipe->ep.wMaxPacketSize);

    hc->host.hc[pipe->pipe_index].toggle_in = 0;
    hc->host.hc[pipe->pipe_index].toggle_out = 0;

    return 0;
}

static int drv_close_pipe(struct uhcd *hcd, upipe_t pipe)
{
    struct stm_hc *hc;

    hc = (struct stm_hc *)hcd->parent.user_data;

    HAL_HCD_HC_Halt(&hc->host, pipe->pipe_index);
    drv_free_pipe_index(hc, pipe->pipe_index);

    LOG_D("free pipe(%d)", pipe->pipe_index);

    return 0;
}

static int drv_urb_enqueue(struct uhcd *hcd, upipe_t pipe, struct urb *r)
{
    struct stm_hc *hc;
    int token;

    hc = (struct stm_hc *)hcd->parent.user_data;

    token = r->is_setup? 0: 1;
    hc->pipe[pipe->pipe_index] = pipe;

    if (!rt_list_isempty(&pipe->urbhead))
    {
        rt_usb_hcd_urb_link(pipe, r, 0);
        return 0;
    }

    LOG_D("enque pipe(%d), addr(%X), size(%d)", pipe->pipe_index, pipe->ep.bEndpointAddress, r->size);

    rt_usb_hcd_urb_link(pipe, r, 1);

    HAL_HCD_HC_SubmitRequest(&hc->host,
                             pipe->pipe_index,
                             (pipe->ep.bEndpointAddress & 0x80) >> 7,
                             pipe->ep.bmAttributes,
                             token,
                             r->buf,
                             r->size,
                             0);

    return 0;
}

static void drv_urb_dequeue(struct uhcd *hcd, upipe_t pipe, struct urb *r)
{
    struct stm_hc *hc;

    hc = (struct stm_hc *)hcd->parent.user_data;

    if (r->is_working)
    {
      uint32_t USBx_BASE = (uint32_t)hc->host.Instance;
      uint32_t ch_num = pipe->pipe_index;


#if 0
    USBx_HC((uint32_t)ch_num)->HCINTMSK = 0u;
    USBx_HC((uint32_t)ch_num)->HCINT    = 0xFFFFFFFFu;
    //USBx_HC((uint32_t)ch_num)->HCTSIZ   = 0u;
#endif

    hc->pipe[pipe->pipe_index] = 0;
#if 0    
    HAL_HCD_HC_Halt(&hc->host, pipe->pipe_index);

    HAL_HCD_HC_Init(&hc->host,
                    pipe->pipe_index,
                    pipe->ep.bEndpointAddress,
                    pipe->inst->address,
                    USB_OTG_SPEED_FULL,
                    pipe->ep.bmAttributes,
                    pipe->ep.wMaxPacketSize);
#endif
//        hc->host.hc[pipe->pipe_index].toggle_in = 0;
//        hc->host.hc[pipe->pipe_index].toggle_out = 0;

        LOG_D("cancel");
    }

    LOG_D("deque pipe(%d), addr(%X), size(%d)", pipe->pipe_index, pipe->ep.bEndpointAddress, r->size);
}

static int drv_start(struct uhcd *hcd)
{
    HCD_HandleTypeDef *hhcd;
    struct stm_hc *hc;

    hc = (struct stm_hc *)hcd->parent.user_data;
    hhcd = &hc->host;

#ifdef BSP_USBH_FS
    hhcd->Instance = USB_OTG_FS;
#else
    hhcd->Instance = USB_OTG_HS;
#endif

#ifdef BSP_USBH_ULPIPHY
    hhcd->Init.phy_itface = HCD_PHY_ULPI;
    hhcd->Init.speed = HCD_SPEED_HIGH;
#else
    hhcd->Init.phy_itface = HCD_PHY_EMBEDDED;
    hhcd->Init.speed = HCD_SPEED_FULL;
#endif
    hhcd->Init.Host_channels = 15;    
    hhcd->Init.dma_enable = ENABLE;
    hhcd->Init.Sof_enable = DISABLE;

    HAL_HCD_Init(hhcd);
    HAL_HCD_Start(hhcd);

    return 0;
}

static void drv_stop(struct uhcd *hcd)
{

}

static const struct uhcd_ops _uhcd_ops =
{
    drv_start,
    drv_stop,
    drv_reset_port,
    drv_pipe_xfer,
    drv_open_pipe,
    drv_close_pipe,
    drv_urb_enqueue,
    drv_urb_dequeue
};

int stm32_usbh_init(void)
{
    uhcd_t uhcd = &_stm_hc.hcd;

    uhcd->parent.type = RT_Device_Class_USBHost;
    uhcd->parent.user_data = &_stm_hc;
    uhcd->ops = &_uhcd_ops;
    uhcd->num_ports = 1;

    _stm_hc.host.pData = &_stm_hc;

    rt_device_register((rt_device_t)uhcd, "usbh", 0);

    return 0;
}
INIT_DEVICE_EXPORT(stm32_usbh_init);
